# -*- coding: utf-8 -*-
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from  torch.utils.data import Dataset, DataLoader
import string
import time
import matplotlib.pyplot as plt


# 获取常用字符包括字母和常用标点
all_letters = string.ascii_letters + " .,;'"
# 获取常用字符数量
n_letters = len(all_letters)  # # 'zhang' == [1,57]
# print('字母表all_letters-->', all_letters, '总长度', n_letters)
# 国家名 种类数
categorys = ['Italian', 'English', 'Arabic', 'Spanish', 'Scottish', 'Irish', 'Chinese', 'Vietnamese', 'Japanese',
             'French', 'Greek', 'Dutch', 'Korean', 'Polish', 'Portuguese', 'Russian', 'Czech', 'German']
# print('国家名-->', categorys, '总种类数', len(categorys))


# 思路分析
# 1 打开数据文件 open(filename, mode='r', encoding='utf-8')
# 2 按行读文件、提取样本x 样本y line.strip().split('\t')
# 3 返回样本x的列表、样本y的列表 my_list_x, my_list_y
# # 原始数据 -> 数据源NameClassDataset --> 数据迭代器DataLoader
def read_data(filename):
    my_list_x, my_list_y = [], []

    # 1 打开数据文件 open(filename, mode='r', encoding='utf-8')
    with open(filename, mode='r', encoding='utf-8') as f:
        for line in f.readlines():
            # 2 按行读文件、提取样本x 样本y line.strip().split('\t')
            x, y = line.strip().split('\t')
            my_list_x.append(x)
            my_list_y.append(y)

    # 3 返回样本x的列表、样本y的列表 my_list_x, my_list_y
    return my_list_x, my_list_y



# 原始数据 -> 数据源NameClassDataset --> 数据迭代器DataLoader
# 构造数据源 NameClassDataset，把语料转换成x y
# 1 init函数 设置样本x和y self.my_list_x self.my_list_y 条目数self.sample_len
# 2 __len__(self)函数  获取样本条数
# 3 __getitem__(self, index)函数 获取第几条样本数据
    # 按索引 获取数据样本 x y
    # 样本x onehot张量化 torch.zeros(len(x), n_letters)
    # 遍历人名 的 每个字母 做成one-hot编码 tensor_x[li][all_letters.find(letter)] = 1
    # 样本y 张量化 torch.tensor(categorys.index(y), dtype=torch.long)
    # 返回tensor_x, tensor_y
class NameClassDataset(Dataset):
    def __init__(self, my_list_x, my_list_y):
        self.my_list_x = my_list_x
        self.my_list_y = my_list_y
        self.sample_len = len(self.my_list_x)

    def __len__(self):
        return self.sample_len

    def __getitem__(self, item):
        # 按索引 获取数据样本 x y
        x = self.my_list_x[item]
        y = self.my_list_y[item]

        # 样本x onehot张量化 torch.zeros(len(x), n_letters)
        tensor_x = torch.zeros(len(x), n_letters) # [3, 57]

        # 遍历人名 的 每个字母 做成one-hot编码
        for line, letter in enumerate(x):
            tensor_x[line][all_letters.find(letter)] = 1

        # 样本y 张量化 torch.tensor(categorys.index(y), dtype=torch.long)
        tensor_y = torch.tensor(categorys.index(y), dtype=torch.long)

        # 返回tensor_x, tensor_y
        return  tensor_x, tensor_y


# 1 读数据到内存  # 2 实例化数据源 # 3 实例化数据迭代器 批次拿数据
def  dm01_test_NameClassDataset():

    # 1 读数据到内存
    my_list_x, my_list_y = read_data('./data/name_classfication.txt')
    print('my_list_x-->', len(my_list_x))
    print('my_list_y-->', len(my_list_y))

    # 2 实例化数据源
    mynameclassdataset = NameClassDataset(my_list_x, my_list_y)
    print('mynameclassdataset-->', mynameclassdataset)
    print('mynameclassdataset[0]', mynameclassdataset[0])

    # 3 实例化数据迭代器 批次拿数据
    mydataloader = DataLoader(dataset=mynameclassdataset, batch_size=1, shuffle=False )
    for x, y in mydataloader:
        print('x-->', x.shape, '\n', x)
        print('x-->', y.shape, '\n', y)
        break


# RNN类 实现思路分析：
# 1 init函数 (self, input_size, hidden_size, output_size, num_layers=1):
    # 准备super() self.rnn self.linear self.softmax=nn.LogSoftmax(dim=-1)
# 2 forward(input, hidden)函数
    # 数据调形 [6,57] ---> [6,1,57] input.unsqueeze(1)
    # 数据经过rnn层 形状变化 [seqlen,1,57],[1,1,128]) -> [seqlen,1,128],[1,1,128]
        # rr, hn=self.rnn(input, hidden)
    # 取最后一个128当人名的特征 [6,1,128] --> [1,128]  tmprr = rr[-1]
    # 数据经过全连接层 [1,128] ---> [1,18]
    # 返回 self.softmax(tmprr), hn
# 3 初始化隐藏层输入数据 inithidden()
#   形状[self.num_layers, 1, self.hidden_size]
class RNN(nn.Module):
    def __init__(self, input_size, hidden_size, output_size, num_layers=1):
        super(RNN, self).__init__()
        self.input_size = input_size
        self.hidden_size = hidden_size
        self.output_size = output_size
        self.num_layers = num_layers

        # 实例化rnn层
        self.rnn = nn.RNN(input_size=input_size, hidden_size=hidden_size, num_layers=num_layers)

        # 实例化全连接层
        self.linear = nn.Linear(in_features=hidden_size, out_features=output_size)

        # 实例化softmax层
        self.softmax = nn.LogSoftmax(dim=-1)

    def forward(self, input, hidden):
        # 数据调形 [6,57] ---> [6,1,57] input.unsqueeze(1)
        input = input.unsqueeze(1)

        # 数据经过rnn层 形状变化 [6,1,57],[1,1,128]) -> [6,1,128],[1,1,128]
        rr, hn = self.rnn(input, hidden)


        # 取最后一个128当人名的特征 [6,1,128] --> [1,128]  tmprr = rr[-1]
        tmprr = rr[-1]

        # 数据经过全连接层 [1,128] ---> [1,18]
        tmprr = self.linear(tmprr)

        # 返回 self.softmax(tmprr), hn
        return  self.softmax(tmprr), hn

    def inithidden(self):
        #   形状[self.num_layers, 1, self.hidden_size]
        return  torch.zeros(self.num_layers, 1, self.hidden_size)



def dm02_test_RNN():
    # 1 rnn模型实例化
    myrnn = RNN(57, 128, 18)
    print('myrnn-->', myrnn)

    # 2 准备数据 input/hidden 'z h a n g a '
    input = torch.randn(6, 57)
    hidden = myrnn.inithidden()

    # 3 给模型喂数据
    # 3-1 一次性送数据 数据形状 [6,57] [1,1,128] ---> [1,18],[1,1,128]
    output, hidden = myrnn(input, hidden)
    print('output-->', output.shape, output)
    print('hidden-->', hidden.shape)

    # 3-2 一个字符一个字符的送数据
    hidden = myrnn.inithidden()  # 注意 第2种方式给模型喂数据,要注意hidden
    for i in range(input.shape[0]):
        tmp = input[i].unsqueeze(0)
        # print('tmp-->', tmp.shape)
        output, hidden = myrnn(tmp, hidden)

    print('output-->', output.shape, output)



###LSTM
class LSTM(nn.Module):
    def __init__(self, input_size, hidden_size, output_size, num_layers=1):
        super(LSTM, self).__init__()
        self.input_size = input_size
        self.hidden_size = hidden_size
        self.output_size = output_size
        self.num_layers = num_layers

        # 实例化rnn层
        self.lstm = nn.LSTM(input_size=input_size, hidden_size=hidden_size, num_layers=num_layers)

        # 实例化全连接层
        self.linear = nn.Linear(in_features=hidden_size, out_features=output_size)

        # 实例化softmax层
        self.softmax = nn.LogSoftmax(dim=-1)

    def forward(self, input, hidden, c0):
        # 数据调形 [6,57] ---> [6,1,57] input.unsqueeze(1)
        input = input.unsqueeze(1)

        # 数据经过rnn层 形状变化 [6,1,57],[1,1,128]) -> [6,1,128],[1,1,128]
        rr, (hn, cn) = self.lstm(input, (hidden, c0))


        # 取最后一个128当人名的特征 [6,1,128] --> [1,128]  tmprr = rr[-1]
        tmprr = rr[-1]

        # 数据经过全连接层 [1,128] ---> [1,18]
        tmprr = self.linear(tmprr)

        # 返回 self.softmax(tmprr), hn
        return  self.softmax(tmprr), hn, cn

    def inithidden(self):
        #   形状[self.num_layers, 1, self.hidden_size]
        h0 = c0 = torch.zeros(self.num_layers, 1, self.hidden_size)
        return h0, c0


def dm03_test_LSTM():
    # 1 rnn模型实例化
    mylstm = LSTM(57, 128, 18)
    print('mylstm-->', mylstm)

    # 2 准备数据 input/hidden 'z h a n g a '
    input = torch.randn(6, 57)
    hidden, c0 = mylstm.inithidden()

    # 3 给模型喂数据
    # 3-1 一次性送数据 数据形状 [6,57] [1,1,128] ---> [1,18],[1,1,128]
    output, hidden, c0 = mylstm(input, hidden, c0)
    print('output-->', output.shape, output)
    print('hidden-->', hidden.shape, c0.shape)

    # 3-2 一个字符一个字符的送数据
    hidden, c0 = mylstm.inithidden()  # 注意 第2种方式给模型喂数据,要注意hidden
    for i in range(input.shape[0]):
        tmp = input[i].unsqueeze(0)
        # print('tmp-->', tmp.shape)
        output, hidden, c0 = mylstm(tmp, hidden, c0)

    print('output-->', output.shape, output)



###GRU
class GRU(nn.Module):
    def __init__(self, input_size, hidden_size, output_size, num_layers=1):
        super(GRU, self).__init__()
        self.input_size = input_size
        self.hidden_size = hidden_size
        self.output_size = output_size
        self.num_layers = num_layers

        # 实例化rnn层
        self.gru = nn.GRU(input_size=input_size, hidden_size=hidden_size, num_layers=num_layers)

        # 实例化全连接层
        self.linear = nn.Linear(in_features=hidden_size, out_features=output_size)

        # 实例化softmax层
        self.softmax = nn.LogSoftmax(dim=-1)

    def forward(self, input, hidden):
        # 数据调形 [6,57] ---> [6,1,57] input.unsqueeze(1)
        input = input.unsqueeze(1)

        # 数据经过rnn层 形状变化 [6,1,57],[1,1,128]) -> [6,1,128],[1,1,128]
        rr, hn = self.gru(input, hidden)

        # 取最后一个128当人名的特征 [6,1,128] --> [1,128]  tmprr = rr[-1]
        tmprr = rr[-1]

        # 数据经过全连接层 [1,128] ---> [1,18]
        tmprr = self.linear(tmprr)

        # 返回 self.softmax(tmprr), hn
        return  self.softmax(tmprr), hn

    def inithidden(self):
        #   形状[self.num_layers, 1, self.hidden_size]
        return  torch.zeros(self.num_layers, 1, self.hidden_size)



def dm04_test_GRU():
    # 1 rnn模型实例化
    mygru = GRU(57, 128, 18)
    print('mygru-->', mygru)

    # 2 准备数据 input/hidden 'z h a n g a '
    input = torch.randn(6, 57)
    hidden = mygru.inithidden()

    # 3 给模型喂数据
    # 3-1 一次性送数据 数据形状 [6,57] [1,1,128] ---> [1,18],[1,1,128]
    output, hidden = mygru(input, hidden)
    print('output-->', output.shape, output)
    print('hidden-->', hidden.shape)

    # 3-2 一个字符一个字符的送数据
    hidden = mygru.inithidden()  # 注意 第2种方式给模型喂数据,要注意hidden
    for i in range(input.shape[0]):
        tmp = input[i].unsqueeze(0)
        # print('tmp-->', tmp.shape)
        output, hidden = mygru(tmp, hidden)

    print('output-->', output.shape, output)


# 思路分析
# 从文件获取数据、实例化数据源对象nameclassdataset 数据迭代器对象mydataloader
# 实例化模型对象my_rnn 损失函数对象mycrossentropyloss=nn.NLLLoss() 优化器对象myadam
# 定义模型训练的参数
#       starttime total_iter_num total_loss  total_loss_list total_acc_num  total_acc_list
# 外层for循环 控制轮数 for epoch_idx in range(epochs)
# 内层for循环 控制迭代次数 for i, (x, y) in enumerate(mydataloader)
    # 给模型喂数据   # 计算损失  # 梯度清零 # 反向传播  # 梯度更新
    # 计算辅助信息
    # 累加总已训练样本 总损失 总准确数
    # 每100次求1下平均损失tmploss 存入total_loss_list列表中 方便画图
    # 每100次求1下平均准确数目tmpacc 存入total_acc_num列表中 方便画图
    # 每2000次训练 打印日志
    # print('轮次:%d, 损失:%.6f, 时间:%d，准确率:%.3f' % (epoch_idx+1, tmploss, time.time()-starttime, tmpacc))
    # 其他 # 预测对错 i_predit_tag = (1 if torch.argmax(output_y).item() == y.item() else 0)
# 模型保存
    # torch.save(my_rnn.state_dict(), './my_rnn_model_%d.bin' % (epoch_idx + 1))
# 返回 平均损失列表total_loss_list, 时间total_time, 平均准确total_acc_list


# 模型训练参数
mylr = 1e-3
epochs = 1

def my_train_rnn():
    # 1 从文件获取数据、实例化数据源对象nameclassdataset 数据迭代器对象mydataloader
    my_list_x, my_list_y = read_data('./data/name_classfication.txt')
    mynameclassdataset = NameClassDataset(my_list_x, my_list_y)
    mydataloader = DataLoader(dataset=mynameclassdataset, batch_size=1, shuffle=True)

    # 2 实例化模型对象my_rnn
    my_rnn = RNN(57, 128, 18)
    print('my_rnn-->', my_rnn)
    # 损失函数对象 mycrossentropyloss=nn.NLLLoss()
    mycrossentropyloss = nn.NLLLoss()
    # 优化器对象myadam
    myadam = optim.Adam(params=my_rnn.parameters(), lr=mylr)

    # 3 定义模型训练的参数
    starttime  = time.time()        # 当前时间
    total_iter_num = 0              # 已经训练的样本个数 累加器
    total_loss  = 0                 # 总的损失  累加器
    total_loss_list = []            # 每100个样本,求一个平均损失 加入list
    total_acc_num  = 0              # 已经训练的样本中,对的数目
    total_acc_list = []             # 每100个样本,求一个平均准确率 加入list

    # 外层for循环 控制轮数 for epoch_idx in range(epochs)
    for epoch_idx in range(epochs):

        # 内层for循环 控制迭代次数 for i, (x, y) in enumerate(mydataloader)
        for  i, (x, y) in enumerate(mydataloader):
            # 给模型喂数据
            output_y, hidden = my_rnn(x[0], my_rnn.inithidden())
            # 计算损失
            myloss = mycrossentropyloss(output_y, y)
            # 梯度清零
            myadam.zero_grad()
            # 反向传播
            myloss.backward()
            # 梯度更新
            myadam.step()

            # 计算辅助信息
            # 累加总已训练样本 总损失 总准确数
            total_iter_num += 1
            total_loss += myloss.item()
            itag = (1 if torch.argmax(output_y).item() == y.item() else 0 )
            total_acc_num += itag

            if total_iter_num % 100 == 0:
                # 每100次求1下平均损失tmploss 存入total_loss_list列表中 方便画图
                tmploss = total_loss/total_iter_num
                total_loss_list.append(tmploss)

                # 每100次求1下平均准确数目tmpacc 存入total_acc_num列表中 方便画图
                tmpacc = total_acc_num / total_iter_num
                total_acc_list.append(tmpacc)

            # 每2000次训练 打印日志
            if total_iter_num % 2000 == 0:
                print('轮次:%d, 损失:%.6f, 时间:%d，准确率:%.3f' % \
                      (epoch_idx+1, tmploss, time.time()-starttime, tmpacc))
        # 模型保存
        torch.save(my_rnn.state_dict(), './my_rnn_model_%d.bin' % (epoch_idx + 1))
        # 返回 平均损失列表 total_loss_list, 时间total_time, 平均准确total_acc_list
        total_time = int(time.time() - starttime)
        return total_loss_list, total_time, total_acc_list


###LSTMTain
def my_train_lstm():
    # 1 从文件获取数据、实例化数据源对象nameclassdataset 数据迭代器对象mydataloader
    my_list_x, my_list_y = read_data('./data/name_classfication.txt')
    mynameclassdataset = NameClassDataset(my_list_x, my_list_y)
    mydataloader = DataLoader(dataset=mynameclassdataset, batch_size=1, shuffle=True)

    # 2 实例化模型对象my_rnn
    my_lstm = LSTM(57, 128, 18)
    print('my_lstm-->', my_lstm)
    # 损失函数对象 mycrossentropyloss=nn.NLLLoss()
    mycrossentropyloss = nn.NLLLoss()
    # 优化器对象myadam
    myadam = optim.Adam(params=my_lstm.parameters(), lr=mylr)

    # 3 定义模型训练的参数
    starttime  = time.time()        # 当前时间
    total_iter_num = 0              # 已经训练的样本个数 累加器
    total_loss  = 0                 # 总的损失  累加器
    total_loss_list = []            # 每100个样本,求一个平均损失 加入list
    total_acc_num  = 0              # 已经训练的样本中,对的数目
    total_acc_list = []             # 每100个样本,求一个平均准确率 加入list

    # 外层for循环 控制轮数 for epoch_idx in range(epochs)
    for epoch_idx in range(epochs):

        # 内层for循环 控制迭代次数 for i, (x, y) in enumerate(mydataloader)
        for  i, (x, y) in enumerate(mydataloader):
            # 给模型喂数据
            h0, c0 = my_lstm.inithidden()
            output_y, h0, c0 = my_lstm(x[0], h0, c0)
            # 计算损失
            myloss = mycrossentropyloss(output_y, y)
            # 梯度清零
            myadam.zero_grad()
            # 反向传播
            myloss.backward()
            # 梯度更新
            myadam.step()

            # 计算辅助信息
            # 累加总已训练样本 总损失 总准确数
            total_iter_num += 1
            total_loss += myloss.item()
            itag = (1 if torch.argmax(output_y).item() == y.item() else 0 )
            total_acc_num += itag

            if total_iter_num % 100 == 0:
                # 每100次求1下平均损失tmploss 存入total_loss_list列表中 方便画图
                tmploss = total_loss/total_iter_num
                total_loss_list.append(tmploss)

                # 每100次求1下平均准确数目tmpacc 存入total_acc_num列表中 方便画图
                tmpacc = total_acc_num / total_iter_num
                total_acc_list.append(tmpacc)

            # 每2000次训练 打印日志
            if total_iter_num % 2000 == 0:
                print('轮次:%d, 损失:%.6f, 时间:%d，准确率:%.3f' % \
                      (epoch_idx+1, tmploss, time.time()-starttime, tmpacc))
        # 模型保存
        torch.save(my_lstm.state_dict(), './my_lstm_model_%d.bin' % (epoch_idx + 1))
        # 返回 平均损失列表 total_loss_list, 时间total_time, 平均准确total_acc_list
        total_time = int(time.time() - starttime)
        return total_loss_list, total_time, total_acc_list

###grutrain
def my_train_gru():
    # 1 从文件获取数据、实例化数据源对象nameclassdataset 数据迭代器对象mydataloader
    my_list_x, my_list_y = read_data('./data/name_classfication.txt')
    mynameclassdataset = NameClassDataset(my_list_x, my_list_y)
    mydataloader = DataLoader(dataset=mynameclassdataset, batch_size=1, shuffle=True)

    # 2 实例化模型对象my_rnn
    my_gru = GRU(57, 128, 18)
    print('my_gru-->', my_gru)
    # 损失函数对象 mycrossentropyloss=nn.NLLLoss()
    mycrossentropyloss = nn.NLLLoss()
    # 优化器对象myadam
    myadam = optim.Adam(params=my_gru.parameters(), lr=mylr)

    # 3 定义模型训练的参数
    starttime  = time.time()        # 当前时间
    total_iter_num = 0              # 已经训练的样本个数 累加器
    total_loss  = 0                 # 总的损失  累加器
    total_loss_list = []            # 每100个样本,求一个平均损失 加入list
    total_acc_num  = 0              # 已经训练的样本中,对的数目
    total_acc_list = []             # 每100个样本,求一个平均准确率 加入list

    # 外层for循环 控制轮数 for epoch_idx in range(epochs)
    for epoch_idx in range(epochs):

        # 内层for循环 控制迭代次数 for i, (x, y) in enumerate(mydataloader)
        for  i, (x, y) in enumerate(mydataloader):
            # 给模型喂数据
            output_y, hidden = my_gru(x[0], my_gru.inithidden())
            # 计算损失
            myloss = mycrossentropyloss(output_y, y)
            # 梯度清零
            myadam.zero_grad()
            # 反向传播
            myloss.backward()
            # 梯度更新
            myadam.step()

            # 计算辅助信息
            # 累加总已训练样本 总损失 总准确数
            total_iter_num += 1
            total_loss += myloss.item()
            itag = (1 if torch.argmax(output_y).item() == y.item() else 0 )
            total_acc_num += itag

            if total_iter_num % 100 == 0:
                # 每100次求1下平均损失tmploss 存入total_loss_list列表中 方便画图
                tmploss = total_loss/total_iter_num
                total_loss_list.append(tmploss)

                # 每100次求1下平均准确数目tmpacc 存入total_acc_num列表中 方便画图
                tmpacc = total_acc_num / total_iter_num
                total_acc_list.append(tmpacc)

            # 每2000次训练 打印日志
            if total_iter_num % 2000 == 0:
                print('轮次:%d, 损失:%.6f, 时间:%d，准确率:%.3f' % \
                      (epoch_idx+1, tmploss, time.time()-starttime, tmpacc))
        # 模型保存
        torch.save(my_gru.state_dict(), './my_gru_model_%d.bin' % (epoch_idx + 1))
        # 返回 平均损失列表 total_loss_list, 时间total_time, 平均准确total_acc_list
        total_time = int(time.time() - starttime)
        return total_loss_list, total_time, total_acc_list

my_path_rnn = './my_rnn_model_1.bin'
my_path_lstm = './my_lstm_model_1.bin'
my_path_gru = './my_gru_model_1.bin'

def lineToTensor(x):
    tensor_x = torch.zeros(len(x),n_letters)
    for li,letter in enumerate(x):
        tensor_x[li][all_letters.find(letter)] = 1
    return tensor_x

def my_predict_rnn(x):
    # 1 输入
    tensor_x =  lineToTensor(x)
    # 2 加载模型
    my_rnn =  RNN(57,128,18)
    my_rnn.load_state_dict(torch.load(my_path_rnn))
    print('my_rnn-->',my_rnn)
    # 3 预测
    with torch.no_grad():
        output,h = my_rnn(tensor_x,my_rnn.inithidden())
        topv,topi =  output.topk(3,1,True)
        print(topv,topi)

        for i in range(3):
            idx = topi[0][i]
            name =  categorys[idx]
            print('\t',name)

def my_predict_lstm(x):
    # 1 输入
    tensor_x =  lineToTensor(x)
    # 2 加载模型
    my_lstm =  LSTM(57,128,18)
    my_lstm.load_state_dict(torch.load(my_path_lstm))
    print('my_lstm-->',my_lstm)
    # 3 预测
    with torch.no_grad():
        hn,cn = my_lstm.inithidden()
        output,hn,cn = my_lstm(tensor_x,hn,cn)
        topv,topi =  output.topk(3,1,True)
        print(topv,topi)

        for i in range(3):
            idx = topi[0][i]
            name =  categorys[idx]
            print('\t',name)

def my_predict_gru(x):
    # 1 输入
    tensor_x =  lineToTensor(x)
    # 2 加载模型
    my_gru =  GRU(57,128,18)
    my_gru.load_state_dict(torch.load(my_path_gru))
    print('my_gru-->',my_gru)
    # 3 预测
    with torch.no_grad():
        output,h = my_gru(tensor_x,my_gru.inithidden())
        topv,topi =  output.topk(3,1,True)
        print(topv,topi)

        for i in range(3):
            idx = topi[0][i]
            name =  categorys[idx]
            print('\t',name)

if __name__ == '__main__':
    # dm01_test_NameClassDataset()
    # dm02_test_RNN()
    # dm03_test_LSTM()
    # dm04_test_GRU()
    # my_train_rnn()
    # my_train_lstm()
    # my_train_gru()
    # my_predict_rnn('zhang')
    my_predict_gru('zhang')


    print('人名分类器 End')





